from turbogears import expose, identity, validate, validators, redirect, controllers, flash
from onlineapidoc.model import (User, Template, Page, Space, Page_History,
                                PAGE_TYPE_DIR, PAGE_TYPE_LINK, PAGE_TYPE_NORMAL_PAGE)
import onlineapidoc.utils as ut
from Cheetah.Template import Template as cheetah_template
from Cheetah.Filters import Filter as cheetah_filter
from Cheetah.ErrorCatchers import ErrorCatcher as cheetah_errorcatcher
from datetime import datetime
import re, difflib, sys
from sqlobject.sqlbuilder import AND
from sqlobject.main import SQLObjectNotFound
from onlineapidoc import patch

class Cheetah_EncodeUnicode_Filter(cheetah_filter):
    def filter(self, val, **kw):
        """Encode Unicode strings, by default for UTF-8"""
        
        if kw.has_key('encoding'):
            encoding = kw['encoding']
        else:
            encoding='utf8'
    
        if val is None:
            filtered = ''
        elif type(val) == type(u''):
            filtered = val.encode(encoding, 'xmlcharrefreplace')
        else:
            filtered = str(val)
            
        if kw.has_key('websafe'):
            filtered = filtered.replace("&", "&amp;") # Must be done first!
            filtered = filtered.replace("<", "&lt;")
            filtered = filtered.replace(">", "&gt;")

        return filtered

class PageController(controllers.Controller):
    @expose()
    def index(self):
        return dict(active_section='page')
    
    @expose(template="onlineapidoc.templates.page.error")
    def page_error(self, error_info):
        """
        Show infomation/error in a page.
        """
        return dict(message=error_info)
    
    @expose()
    def new_page_add(self, name, template_id, type, space_id, parent_id, link_dest_id):
        """
        Store new page attributes in cookies and load the edit template for content edit.
        """
        ut.set_session('new_page_name', name)
        ut.set_session('new_page_type', type)
        ut.set_session('new_page_space', space_id)
        ut.set_session('new_page_parent', parent_id)
        ut.set_session('new_page_template', template_id)
        
        ut.set_cookie('favorite_template', template_id)

        if PAGE_TYPE_DIR == int(type):
            return self._do_new_page_commit(None)
        elif PAGE_TYPE_LINK == int(type):
            return self._do_new_page_commit(link_dest_id)

        #normal page
        template = Template.get(template_id)

        dict = {}
        dict[ut.dict_c_mark["title"]] = name
        dict[ut.dict_c_mark["action"]] = "/page/new_page_commit"

        return str(self._gen_page_by_template(template.edit_template, dict))

    @expose()
    def new_page_commit(self, **kw):
        """
        Commit the new page's content and attributes to database.
        """
        return self._do_new_page_commit(kw)

    @identity.require(identity.has_permission("page-edit"))
    @expose()
    def page_edit(self, page_id):
        """
        Get content from database and load edit template to generate a edit interface.
        """
        page = Page.get(page_id)
        
        dict = self._get_page_content_dict(page.content)
        dict[ut.dict_c_mark["title"]] = page.name
        dict[ut.dict_c_mark["action"]] = "/page/page_edit_commit/"+page_id

        return str(self._gen_page_by_template(page.template.edit_template, dict))

    
    @expose()
    def page_edit_commit(self, page_id, **kw):
        """
        Commit the modified content of page to database.
        """
        page = Page.get(page_id)
        edit_user = User.get(identity.current.user.id)

        newcontent = (repr(kw)).replace("\\r\\n", "\r\n") + "\r\n"
        oldcontent = (page.content).replace("\\r\\n", "\r\n") + "\r\n"

        diffres = difflib.unified_diff(newcontent.splitlines(1), oldcontent.splitlines(1), n=0)

        if page.history_pages:
            h = page.history_pages[0]
            h.set(version=h.version+1,
                  content=repr(list(diffres)[2:]),
                  time=page.last_edit_time)
        else:
            h = Page_History(page=page,
                    version=0,
                    content=repr(list(diffres)[2:]),
                    time=page.last_edit_time)

        page.set(content=repr(kw),
                 built=False,
                 last_edit_user=edit_user,
                 last_edit_time=datetime.now())

        raise redirect("/page/page_view/" + str(page.id))
    
    @expose(template="onlineapidoc.templates.space.index")
    def page_view(self, page_id):
        """
        Show page's content or show directory's files or show a linked page's content
        Because of the auto generated link will point to an address as page's id plus "html" like "id.html",
        so at first in this function, I will strip the "html"
        """
        page_id = int(str(page_id).rstrip('.html'))
        
        if not page_id:
            raise redirect('/space')
        try:
            page = Page.get(page_id)
        except SQLObjectNotFound:
            return self.page_error(_("Can not found that page!"))

        if PAGE_TYPE_LINK == page.type:
            #type is link
            return self.page_view_by_template(int(page.content))
        elif PAGE_TYPE_NORMAL_PAGE == page.type:
            #type is page
            return self.page_view_by_template(page_id)

        f_template = ut.get_cookie('favorite_template', 0)
        templates = Template.select(orderBy='name')

        sdict = ut.SortedDict()
        self._get_parent_page_path(page, sdict)
        
        return dict(active_section='space',
                    space=page.space,
                    templates=templates,
                    page_path=sdict,
                    favorite_template=int(f_template),
                    current_page=page,
                    lang_family = ut.get_page_family_lang(page))

    @expose(template="onlineapidoc.templates.page.setting")
    def page_setting(self, page_id):
        """
        Show setting page
        User would change the page parent or page name or template
        """
        page = Page.get(page_id)
        
        if page.parent_page:
            parent = page.parent_page.id
        else:
            parent = None
            
        if (page.type == PAGE_TYPE_DIR) and eval(page.content):
            index = (ut.get_matching_lang_page(Page.get(int(eval(page.content))), page.space.language)).id
        else:
            index = None

        templates = Template.select(orderBy='name')
        
        return dict(active_section='space',
                    templates=templates,
                    page=page,
                    parent=parent,
                    index=index)

    @expose()
    def page_setting_commit(self, page_id, name, tmpl_or_lnk_id, parent_id, index_id):
        """
        Commit the modified page's attributes to database.
        """
        page = Page.get(page_id)
        edit_user = User.get(identity.current.user.id)
        
        '''
        current parent point to root and the new parent is not or the new parent is not same as current one.
        '''
        if (not page.parent_page and parent_id) or (page.parent_page and parent_id != page.parent_page.id):
            if parent_id:
                parent_page = Page.get(parent_id)   #got the new parent

                '''
                Validate whether the parent is a normal page or a link page
                '''
                if PAGE_TYPE_NORMAL_PAGE == parent_page.type or PAGE_TYPE_LINK == parent_page.type:
                    return self.page_error(_(r"The parent page must be a dir only!"))
    
                '''
                Validate whether the parent that user submit is current page's child.
                The page's parent must not be its child
                '''
                tmpdict = ut.SortedDict()
                self._get_parent_page_path(parent_page, tmpdict)
    
                if int(page_id) in tmpdict.keys():
                    return self.page_error(_(r"The page's parent must not be its child!"))
    
                parent = Page.get(parent_id)
            else:
                parent = None   #root
    
            if parent:
                '''
                Because of the parent is not the root of every space, so get every language page and its new parent
                '''
                ipage = ut.get_primary_page(page)   #primary language page
                dpages = ut.SortedDict()    #a dict store the every page's id and its new parent's id
                ppage = None

                '''
                At first, the primary language page find its new parent and then add to dict.
                '''
                ppage = ut.get_matching_lang_page(parent, ipage.space.language)
                    
                if not ppage:
                    return self.page_error(_(r"There is no matched parent page in the reference space!"))
                else:
                    dpages[ipage.id] = ppage.id

                '''
                In cycle to find the children page's parent
                '''
                for child in ipage.link_copies:
                    ppage = ut.get_matching_lang_page(parent, child.space.language)
                    if not ppage:
                        return self.page_error(_(r"There is no matched parent page in the reference space!"))
                    else:
                        dpages[child.id] = ppage.id

                for cpid,ppid in dpages.items():
                    cp = Page.get(cpid)
                    pp = Page.get(ppid)
    
                    cp.set(parent_page=pp,
                        built=False,   #set to false for the building next time
                        last_edit_user=edit_user,
                        last_edit_time=datetime.now())

            else:
                page.set(parent_page=None,
                        built=False,   #set to false for the building next time
                        last_edit_user=edit_user,
                        last_edit_time=datetime.now())

                for child in page.link_copies:
                    child.set(parent_page=None,
                        built=False,   #set to false for the building next time
                        last_edit_user=edit_user,
                        last_edit_time=datetime.now())


        if name != page.name:
            page.set(name=name,
                built=False,   #set to false for the building next time
                last_edit_user=edit_user,
                last_edit_time=datetime.now())
    
        if (PAGE_TYPE_NORMAL_PAGE == page.type) and (tmpl_or_lnk_id != page.template.id):
            template = Template.get(tmpl_or_lnk_id)
            page.set(template=template)
        elif PAGE_TYPE_LINK == page.type:
            page.set(content=tmpl_or_lnk_id)
        
        '''
        Update the default page for directory
        '''
        if PAGE_TYPE_DIR == page.type:
            if index_id:
                try:
                    indexpage = Page.get(index_id)
                except SQLObjectNotFound:
                    return self.page_error(_("No such page!"))
                else:
                    if len(indexpage.link_copies) != len(indexpage.space.children_space):
                        return self.page_error(_("Can not set page that have not been translated as default page!"))
                    ipage = ut.get_primary_page(page)
                    ipage.set(content=str(indexpage.id))
                    for child in ipage.link_copies:
                        child.set(content=str(indexpage.id))
            else:
                '''
                Some other codes will use the content, so set the 'None' str to content column but not None
                '''
                ipage = ut.get_primary_page(page)
                ipage.set(content='None')
                for child in ipage.link_copies:
                        child.set(content='None')

        if page.parent_page:
            raise redirect("/page/page_view/" + str(parent.id))
        else:
            raise redirect('/space')

    @expose()
    def page_view_history(self, page_id):
        try:
            page = Page.get(page_id)
        except SQLObjectNotFound:
            return self.page_error(_("Can not found that page!"))
        
        h = page.history_pages[0]
        curcontent = (page.content).replace("\\r\\n", "\r\n") + "\r\n"
        
        diffres = "".join((patch.patch(curcontent.splitlines(1), eval(h.content)))[0])

        dict = self._get_page_content_dict((diffres.replace("\r\n", "\\r\\n")).rstrip('\\r\\n'))
        dict[ut.dict_c_mark["title"]] = page.name
        dict[ut.dict_c_mark["page_encoding"]] = "utf-8"
        dict[ut.dict_c_mark["page_release"]] = True
        dict[ut.dict_c_mark["page_ext_info"]] = True

        return str(self._gen_page_by_template(page.template.view_template, dict))

    @expose(template="onlineapidoc.templates.page.diff")
    def page_view_diff(self, page_id):
        try:
            page = Page.get(page_id)
        except SQLObjectNotFound:
            return self.page_error(_("Can not found that page!"))

        h = page.history_pages[0]
        
        curcontent = (page.content).replace("\\r\\n", "\r\n") + "\r\n"
        
        diffres = "".join((patch.patch(ut.unilist(curcontent.splitlines(1)), ut.unilist(eval(h.content))))[0])
        
        htmdiff = difflib.HtmlDiff()
        
        return dict(page = page,
                active_section = 'space',
                difftable = htmdiff.make_table(ut.unilist(curcontent.splitlines(1)), diffres.splitlines(1), "working copy", "V" + str(h.version), True, 1))
    
    @expose()
    def page_view_by_template(self, page_id, isrls=False, encoding="utf-8", ext_info=True):
        page = Page.get(page_id)

        #these variable will be used in the auto link generation
        self.__view_page_space = page.space.id
        self.__view_page_name = page.name
        self.__view_page_id = page.id

        dict = self._get_page_content_dict(self._gen_auto_inner_link(page.content))
        dict[ut.dict_c_mark["title"]] = page.name

        #view page in release mode or not
        if not isrls:
            dict[ut.dict_c_mark["action"]] = "/page/page_edit/" + str(page.id)
            tmpdict = ut.SortedDict()
            self._get_parent_page_path(page, tmpdict, False)
            dict[ut.dict_c_mark["page_path"]] = tmpdict
            dict[ut.dict_c_mark["page_lang"]] = ut.get_page_family_lang(page)
            dict[ut.dict_c_mark["space_name"]] = page.space.name
            dict[ut.dict_c_mark["space_id"]] = page.space.id
        else:
            dict[ut.dict_c_mark["page_encoding"]] = encoding
            dict[ut.dict_c_mark["page_release"]] = isrls
            
        dict[ut.dict_c_mark["page_ext_info"]] = ext_info

        return str(self._gen_page_by_template(page.template.view_template, dict, encoding))
    
    @expose()
    def page_delete(self, page_id):
        page = Page.get(page_id)

        if len(page.link_copies) > 0:
            raise redirect('/page/destroy_family/' + str(page.id))

        self._do_delete_page(page)

        raise redirect('/space')

    @expose(template="onlineapidoc.templates.page.destroy_family")
    def destroy_family(self, page_id):
        page = Page.get(page_id)

        return dict(active_section='space', page=page)

    @expose()
    def destroy_family_commit(self, page_id):
        page = Page.get(page_id)

        for link_page in page.link_copies:
            self._do_delete_page(link_page)

        self._do_delete_page(page)

        raise redirect('/space')
    
    @expose(template="onlineapidoc.templates.page.translate")
    def page_translate(self, page_id):
        page = Page.get(page_id)

        return dict(active_section='space',
                    page=page,
                    lang_family = ut.get_page_family_lang_no_trans(page))

    @expose()
    def page_translate_commit(self, page_id, space_id, name):
        page = Page.get(page_id)
        space = Space.get(space_id)

        parent_lang = None
        if page.parent_page:
            #find the matched parent in the reference space
            parent_lang = ut.get_matching_lang_page(page.parent_page, space.language)
            if not parent_lang:
                return self.page_error(_(r"There is no matched parent page in the reference space!"))
        
        newpage = ut.do_duplicate_page(page, space, True, parent_lang)
        
        newpage.set(name = name)
        
        if newpage.type == PAGE_TYPE_NORMAL_PAGE:
            raise redirect('/page/page_edit/' + str(newpage.id))
        else:
            raise redirect('/page/page_view/' + str(newpage.id))

    def _do_new_page_commit(self, content):
        edit_user = User.get(identity.current.user.id)

        parent = None
        template = None

        if ut.get_session('new_page_parent'):
            parent = Page.get(ut.get_session('new_page_parent'))

        if ut.get_session('new_page_template'):
            template = Template.get(ut.get_session('new_page_template'))
            
        if ut.get_session('new_page_space'):
            space = Space.get(ut.get_session('new_page_space'))

        new_page = Page(name=ut.get_session('new_page_name'),
                        content=repr(content),
                        space=space,
                        parent_page=parent,
                        type=int(ut.get_session('new_page_type')),
                        template=template,
                        last_edit_user=edit_user,
                        version=1,
                        primary_copy=None)

        raise redirect("/page/page_view/" + str(new_page.id))

    def _get_parent_page_path(self, page, sdict, page_self=True):
        if page_self:
            sdict.insert(page.id, page.name)
        
        if page.parent_page:
            #sdict.insert(page.parent_page.id, page.parent_page.name)
            return self._get_parent_page_path(page.parent_page, sdict)

        return None
    
    def _get_page_content_dict(self, content):
        if content:
            dict = eval(content)
        else:
            dict = {}
        
        return dict
    
    def _gen_page_by_template(self, template, sdict, encoding="utf-8"):
        return cheetah_template(template.encode(encoding, 'xmlcharrefreplace'),
                                searchList=[sdict],
                                filter=Cheetah_EncodeUnicode_Filter)
    
    def _gen_link_callback(self, m):
        """
        Call back function for the link generation regex.
        At first, I will try to scan all groups and then set value for strtitle, strdesc, strid.
        If got the strtitle, I will find the matching title page.
        
        the description in [[]] will be mark as emphasize, I will add b tag.
        """
        LINKSTR = r"<a href=\'%(id)s.html%(anchor)s\'>%(desc)s</a>" #the text string symbol may conflict so use the ' but not " for HTML link text
        strid = ""
        stranchor = ""
        strdesc = ""
        strtitle = ""
        matchstr = ""
        list_match = []
        bemphasize = False
        
        if m.group('id'):   #[[^id#foo|bar]]
            '''
            if the language of current page is not same with the link page,
            try to find the matching language of the link page
            '''
            cur_page = Page.get(self.__view_page_id)
            link_page = Page.get(int(m.group('id')))
            
            if link_page.space.language != cur_page.space.language:
                link_page = ut.get_matching_lang_page(link_page, cur_page.space.language)

            if link_page:
                strid = str(link_page.id)
            else:
                #there is no matched page with current language, so accept the id writtern in source
                strid = m.group('id')

            if m.group('anchor'):
                stranchor = m.group('anchor')
            if m.group('descx'):
                bemphasize = True
                strdesc = m.group('descx')
        elif m.group('descy'):  #[[foo|bar]]
            bemphasize = True
            matchstr = eval(r"u'" + m.group('descy') + r"'")  #convert the data from "\u.." to normal unicode string
            list_match = matchstr.split('|', 2)
            if len(list_match) == 2:
                strtitle = list_match[0]
                strdesc = list_match[1]
        elif m.group('ufunc'):
            matchstr = m.group('ufunc')
        elif m.group('lfunc'):
            matchstr = m.group('lfunc')
        
        if matchstr and len(list_match) != 2:   #the matching string does not contain "|"
            strdesc = strtitle = matchstr

        if strtitle:
            if strtitle == self.__view_page_name:
                return strdesc
            if self.__view_page_space:
                #select result
                psr = Page.select(AND(Page.q.name==strtitle,
                                      Page.q.spaceID==self.__view_page_space,
                                      Page.q.type==PAGE_TYPE_NORMAL_PAGE))[:1];
                if psr.count():
                    strid = str(psr[0].id)

        if strid:
            return LINKSTR % {'id': strid, 'anchor': stranchor, 'desc': strdesc}
        elif bemphasize:
            return "<b>" + strdesc + "</b>"
        else:    
            return strdesc

    def _gen_auto_inner_link(self, mark_str):
        """
        The auto link mark format is [[^ID|Description]].
        ID is the id of page.
        Description is the text shown as a link in page.
        """
        if mark_str:
            rexstr = re.sub(r'\[\[\^(?P<id>[0-9]+)(?P<anchor>#|.*?)\|(?P<descx>.*?)\]\]|\[\[(?P<descy>.*?)\]\]|\b(?P<ufunc>[A-Z]\w*[A-Z]+\w+)|\b(?P<lfunc>\w+\_+\w+)',
                            self._gen_link_callback, mark_str)

            return rexstr
        else:
            return None

    def _do_delete_page(self, page):
        """
        Delete sub pages recursely
        """
        if page.type==PAGE_TYPE_DIR:
            #delete sub pages
            for sub_page in page.sub_pages:
                self._do_delete_page(sub_page)
    
            #delete self
            page.destroySelf()
        else:
            page.destroySelf()
    
        return None